上医治未病:BFE对正则表达式的思考
“上医治未病,中医治欲病,下医治已病”(《黄帝内经》)。
在七层负载均衡领域,正则表达式被广泛的使用于路由转发规则的配置。在Nginx和OpenResty中,都依赖于正则表达式的机制。
在实践中,我们发现正则表达式存在以下两个严重问题:
(1) 配置难以维护。正则表达式存在严重的可读性问题。用正则表达式编写的转发条件很难看懂,且容易存在二义性,我们经常发现,对于一个人编写的分流条件,其他人很难接手继续维护。
(2) 性能存在隐患。对于编写不当的正则表达式,可能在特定的流量特征下会出现严重的性能退化。在线上曾经发生过这样的情况:原本每秒可以处理几千个请求的服务,由于增加了一个正则表达式描述,其性能下降到每秒只能处理几十个请求。
针对正则表达式所存在的性能隐患,已经有不少分析的文章,可以搜索“正则表达式回溯漏洞”或“正则表达式回溯陷阱”。在解决这个问题方面,大家有不同的思路。
一种思路是在发生性能方面的问题后,使用工具来快速定位问题。如OpenResty官网Blog在7月份发表了文章《Tracing the Slowest PCRE Regular Expressions in OpenResty or Nginx Processes》(https://blog.openresty.com/en/ngx-slowest-pcre-regexes/),可以实时的对Nginx和OpenResty的进程进行分析,并快速定位那些有问题的正则表达式。
遗憾的是,以上的思路在大规模多租户的场景并不适用。在BFE所部署的场景中,经常并存很多租户。这些租户的管理员可以随时自助的提交转发配置,而这些配置在提交后会在几分钟内下发生效。如果某个租户的管理员提交了有问题的条件表达式,会很快在多个租户所复用的BFE集群生效,并对于转发集群整体的稳定性产生威胁。在这种情况下,即使有很好的工具来快速发现和定位有问题的表达式,也无法阻止事故的发生。
在BFE的设计中,我们使用了另外一种思路:尽量减少正则表达式的使用。
在BFE中,引入了自定义的条件表达式(Condition Expression)。条件表达式的详情见 https://github.com/bfenetworks/bfe/tree/develop/docs/zh_cn/condition,下面只做简要介绍。
在条件表达式的设计中引用了以下思想。
(1)在表述中明确指定所使用的HTTP请求字段,以此提升可读性。例如,从req_path_prefix_in()这样的名字中可以立刻看出是针对请求中的Path部分进行前缀匹配;从req_method_in()中可以看出是针对请求的“method”字段进行匹配。
(2)控制计算的复杂度,降低性能退化的风险。条件表达式主要使用精确匹配、前缀匹配、后缀匹配等计算方式,这些计算方式的计算复杂度都较低。
条件表达式的例子如下:
//
如果请求域名是
"bfe-networks.com"
且请求方法是
"GET",
返回
true
req_host_in("bfe-networks.com") &&
req_method_in("GET")
在以上例子中,
req_host_in()
和
req_method_in()
是
2
个
BFE
内置的条件原语,类似这样的条件原语有几十个,可以描述各种可能的匹配条件(详情可以参考
https://github.com/bfenetworks/bfe/blob/develop/docs/zh_cn/condition/condition_primitive_index.md
)。
多个条件原语之间可以使用与、或、非来组合,成为条件表达式。
条件表达式的机制,在多租户大流量场景下经过了多年的验证,取得了非常好的效果。在上千个租户的情况下,各租户的管理员都可以很好的维护自己的配置,并且对公用转发平台的稳定性没有影响。
BFE转发模型的详细说明,可以参考历史文章:
欢迎关注“BFE开源项目”微信公众号,获得本项目的更多信息。谢谢!